# Lever 7 - stop and start a low-risk service whose host process is hot. # Walks service to PID directly. Every candidate is svchost hosted, and # svchost can never appear in a filtered offender list. $eventLogSource = 'HighCPURemediationRecycleService' $eventLogName = 'Application' if (-not [System.Diagnostics.EventLog]::SourceExists($eventLogSource)) { New-EventLog -LogName $eventLogName -Source $eventLogSource -ErrorAction SilentlyContinue } function Write-RemediationEvent { param([string]$Message) Write-Output $Message try { Write-EventLog -LogName $eventLogName -Source $eventLogSource -EntryType Information -EventId 1000 -Message $Message -ErrorAction Stop } catch { # Tolerated. Losing an audit line must not stop remediation itself. } } # Fixed allow-list, not a computed risk score. Each entry is included for two # reasons together, safe to interrupt with no data loss from a stop, and a # plausible spin candidate known or credible to loop under contention. $recyclable = @( 'wuauserv', 'BITS', 'DPS', 'Spooler', 'Audiosrv', 'AudioEndpointBuilder', 'Themes', 'TabletInputService', 'TrkWks', 'WbioSrvc', 'WSearch' ) $before = @{} foreach ($p in (Get-Process -ErrorAction SilentlyContinue)) { try { $before[$p.Id] = $p.TotalProcessorTime.Ticks } catch { } } Start-Sleep -Seconds 2 $hotPids = @() foreach ($p in (Get-Process -ErrorAction SilentlyContinue)) { try { if (-not $before.ContainsKey($p.Id)) { continue } $cores = ($p.TotalProcessorTime.Ticks - $before[$p.Id]) / 20000000.0 if ($cores -ge 0.5) { $hotPids += $p.Id } } catch { } } if ($hotPids.Count -eq 0) { Write-RemediationEvent 'skip: no hot process' return } $allServices = Get-CimInstance Win32_Service -ErrorAction Stop foreach ($hotPid in $hotPids) { # A host with more than one service cannot be attributed, so it is passed # over rather than guessed at. Modern builds split svchost one service per # process, so a single match is the normal case. $hosted = @($allServices | Where-Object { $_.ProcessId -eq $hotPid }) if ($hosted.Count -ne 1) { continue } $svcInfo = $hosted[0] if ($recyclable -notcontains $svcInfo.Name) { continue } # A disabled service cannot be brought back up once stopped. if ($svcInfo.StartMode -eq 'Disabled') { Write-RemediationEvent ('skip: {0} is disabled' -f $svcInfo.Name) continue } $svc = Get-Service -Name $svcInfo.Name -ErrorAction Stop if (-not $svc.CanStop) { continue } $dependents = @($svc.DependentServices | Where-Object { $_.Status -eq 'Running' }) if ($dependents.Count -gt 0) { Write-RemediationEvent ('skip: {0} has running dependents' -f $svcInfo.Name) continue } $svc.Stop() $svc.WaitForStatus('Stopped', [TimeSpan]::FromSeconds(20)) $svc.Start() $svc.WaitForStatus('Running', [TimeSpan]::FromSeconds(20)) Write-RemediationEvent ('recycled: {0} in PID {1}' -f $svcInfo.Name, $hotPid) }